﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Core;
using Silvermoon.Controls;
using System.Diagnostics;
using System.Threading;

namespace Silvermoon.Animations
{
    #region AnimEventArgs

    /// <summary>
    /// EventArgs for animations.
    /// </summary>
    /// <remarks>
    /// This class also proceeds the actual animation.
    /// </remarks>
    public class AnimEventArgs : EventArgs
    {
        #region fields

        private double acceleration = 3.8f;

        private float smax;
        private int startTime;

        #endregion
        #region properties

        /// <summary>
        /// Specifies the acceleration behavior.
        /// If set to 1.0, the acceleration is linerar; 
        /// if set to larger than 1.0 (>1.0) provide a decelleration, where the decelleration is proportional to the specified value;
        /// if set to smaller than 1.0 (&lt1.0) provide an acceleration where the acceleration increases with lower values.
        /// Zero or negative values are not allowed an cause an ArgumentOutOfRangeException.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException"/> if Value is negative or zero.
        public double Acceleration
        {
            get { return acceleration; }
            set
            {
                if (acceleration != value)
                {
                    if (acceleration <= 0) throw new ArgumentOutOfRangeException("Acceleration", "Value must be positive.");
                    acceleration = value;
                    smax = CalculateS(Duration);
                }
            }
        }

        /// <summary>
        /// Gets the elapsed time.
        /// </summary>
        public int Elapsed {get;private set;}

        /// <summary>
        /// Gets the start delay in milliseconds.
        /// </summary>
        public int StartDelay { get; private set; }

        /// <summary>
        /// Gets the total duration for this animation in milliseconds.
        /// </summary>
        public int Duration { get; private set; }

        /// <summary>
        /// Gets the value at the beginning of the animation.
        /// </summary>
        public int BeginValue { get; private set; }

        /// <summary>
        /// Gets the value at the end of the animation.
        /// </summary>
        public int EndValue { get; private set; }

        /// <summary>
        /// Gets the current animation value which is between BeginValue and EndValue.
        /// </summary>
        public int Value { get; private set; }

        /// <summary>
        /// Gets whether animation is completed.
        /// </summary>
        public bool IsCompleted { get; private set; }

        /// <summary>
        /// Gets whether animation is in progress.
        /// </summary>
        public bool IsRunning { get; private set; }

        #endregion
        #region ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="AnimEventArgs"/> class.
        /// </summary>
        /// <param name="duration">The duration.</param>
        /// <param name="beginValue">The begin value.</param>
        /// <param name="endValue">The end value.</param>
        /// <param name="startDelay">The start delay.</param>
        internal AnimEventArgs(int duration, int beginValue, int endValue, int startDelay)
        {
            this.Duration = duration;
            this.BeginValue = beginValue;
            this.EndValue = endValue;
            this.StartDelay = startDelay;
            smax = CalculateS(duration);
        }

        #endregion
        #region methods

        /// <summary>
        /// Invokes the animation asynchronously.
        /// </summary>
        public void InvokeAsync()
        {
            IsRunning = true;
            Screen.Current.Tick += OnTimerTick;
        }

        /// <summary>
        /// Invokes the animation synchronously.
        /// </summary>
        public void Invoke()
        {
            IsRunning = true;
            Screen.Current.Tick += OnTimerTick;
            Screen host = Screen.Current;
            while (!IsCompleted)
            {
                host.ProgressTimer();
                // Tick(Environment.TickCount);
                if (!IsRunning) break;
                host.Render();
                Thread.Sleep(2);
            }
        }

        /// <summary>
        /// Aborts the animation and leaves the current state.
        /// see also <seealso cref="Complete"./>
        /// </summary>
        public void Abort()
        {
            Screen.Current.Tick -= OnTimerTick;
            if (IsRunning)
            {
                //if (Progress != null) Progress(this, this);
                OnComplete();               
            }
            IsRunning = false;
        }

        /// <summary>
        /// Stops the animation and completes to the final state.
        /// see also <seealso cref="Abort"./>
        /// </summary>
        public void Finish()
        {
            Screen.Current.Tick -= OnTimerTick;
            // go to the final state:
            OnProgress(Duration);
            IsRunning = false;
        }

        private void OnTimerTick(object sender, TimeEventArgs e)
        {
            Tick(e.Tick);

        }

        private void Tick(int tick)
        {
            if (!IsRunning) return;
            if (startTime == 0)
            {
                startTime = tick;
                IsRunning = true;
            }

            int elapsed = tick - startTime;
            this.Elapsed = elapsed;
            if (elapsed >= StartDelay)
            {
                int t = elapsed - StartDelay;
                OnProgress(t);
            }
        }


        /// <summary>
        /// handles the current progress.
        /// </summary>
        /// <param name="duration">The duration since animation started.</param>
        protected virtual void OnProgress(int time)
        {
            try
            {
                if (time > Duration) time = Duration;
                Value = GetValueFromDuration(time);

                IsCompleted = time == Duration;
                if (IsCompleted) Screen.Current.Tick -= OnTimerTick;

                if (Progress != null) Progress(this, this);
                if (time == Duration) OnComplete();
            }
            catch (Exception)
            {
                // if something bad happens, cleanup the timer:
                OnComplete();
                throw;
            }
        }

        /// <summary>
        /// Occurs after animation is completed.
        /// </summary>
        protected virtual void OnComplete()
        {
            Screen.Current.Tick -= OnTimerTick;

            // reset for possible reuse:
            startTime = 0;
            IsRunning = false;
            if (Complete != null) Complete(this, this);
        }

        private int GetValueFromDuration(int t)
        {
            // int value = (t * (EndValue - BeginValue)) / Duration + BeginValue;
            float s = CalculateS(Duration - t);
            return (int)(EndValue - s / smax * (EndValue - BeginValue));
        }

        private float CalculateS(int t)
        {
            return (float)Math.Pow(t, acceleration);
        }


        #endregion
        #region events

        /// <summary>
        /// Occurs when the animation is progressing another state.
        /// </summary>
        public event EventHandler<AnimEventArgs> Progress;

        /// <summary>
        /// Occurs after animation is completed.
        /// </summary>
        public event EventHandler<AnimEventArgs> Complete;

        #endregion
    }

    #endregion
    #region ControlAnimEventArgs

    /// <summary>
    /// extended AnimEventArgs that holds additional information for animating controls.
    /// </summary>
    internal class ControlAnimEventArgs : AnimEventArgs
    {

        /// <summary>
        /// Gets the controls that are affected by this animation.
        /// </summary>
        public IEnumerable<Control> Controls { get; private set; }

        public TransitionMode Mode { get; private set; }

        public IEnumerable<Transformation> Transformations { get; internal set; }

        public ControlAnimEventArgs(int duration, int beginValue, int endValue, int startDelay, IEnumerable<Control> controls, TransitionMode mode)
            : base(duration, beginValue, endValue, startDelay)
        {
            if (controls == null) throw new ArgumentNullException("controls");

            Controls = controls;
            this.Mode = mode;
        }

    }
    #endregion
}
